package info.niwota.web.apache;

import info.niwota.commons.FileItem;
import info.niwota.commons.io.Files;
import info.niwota.web.ApplicationContext;
import info.niwota.web.Debug;
import info.niwota.web.Preferences;
import info.niwota.web.R;
import info.niwota.web.SiteUtil;
import info.niwota.web.app.AppHelper;
import info.niwota.web.app.ResourceInfo;
import info.niwota.web.bg.BackgroundImages;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

import x.org.apache.http.HttpRequest;
import x.org.apache.http.HttpResponse;
import x.org.apache.http.protocol.HttpContext;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

/**
 * assets/ script/ style/ image/ pub/ doc/
 * 
 * icon/ application icon
 * 
 * wallpaper/
 * 
 * res/ drawable/ raw/
 * 
 * resource/ class resource
 * 
 * uploaded/
 * 
 * @author qiangli
 * 
 */
public class ResourceHandler extends BaseHandler {
	private static final String TAG = "ResourceHandler";

	private PackageManager pm = null;

	public ResourceHandler(Context ctx) {
		super(ctx);
		this.pm = ctx.getPackageManager();
	}

	@Override
	public boolean handle(String path, HttpRequest req, HttpResponse res,
			HttpContext context) {

		Uri uri = Uri.parse("uri://" + path);
		String p = uri.getPath(); // remove query params
		if (Debug.DEBUG) {
			Log.d(TAG, "handle path: " + p + " uri: " + uri);
		}

		try {
			//
			// dispatch
			//
			if (p.equals("/icon")) {
				// logo
				sendDrawable(req, res, "icon", R.drawable.icon, "image/png");	
				
				return true;
			}
			// /icon/<pkg>
			if (handleAppIcon(uri, p, req, res, context)) {
				return true;
			}
			// /wallpaper
			if (handleWallpaper(uri, p, req, res, context)) {
				return true;
			}
			// /avatar
			if (handleAvatar(uri, p, req, res, context)) {
				return true;
			}
			
			// /siteinfo
			if (handleSiteInfo(uri, p, req, res, context)) {
				return true;
			}
			// /assets/ ...
			if (handleAsset(uri, p, req, res, context)) {
				return true;
			}
			// /res/
			if (handleDrawableRes(uri, p, req, res, context)) {
				return true;
			}
			// /resource/
			if (handleResource(uri, p, req, res, context)) {
				return true;
			}

			// /tmp
			if (handleTemp(uri, p, req, res, context)) {
				return true;
			}
			
			//
			if (Debug.DEBUG) {
				Log.d(TAG, "Resource not found, continue: " + uri);
			}
			return false;
		} catch (Exception e) {
			e.printStackTrace();
		}
		// discard request if exception
		return true;
	}
	
	private boolean handleSiteInfo(Uri uri, String p, HttpRequest req,
			HttpResponse res, HttpContext context) throws Exception {
		//use /home
		if (p.startsWith("/siteinfo")) {
			
			JSONObject data = Preferences.getSiteInfo(ctx);
			sendJSONData(req, res, context, data);
			
			return true;
		}
		return false;
	}
	
	private boolean handleAvatar(Uri uri, String p, HttpRequest req,
			HttpResponse res, HttpContext context) throws Exception {
		if (p.equals("/avatar") || p.startsWith("/avatar.")) {
			
			final String def = "/res/drawable/ic_contact_picture.png";
			//
			String peer = uri.getQueryParameter("peer");
			String jid = ApplicationContext.getJid();
			boolean islocal = (TextUtils.isEmpty(peer) || peer.equals(jid));
			
			File avatar = null;
			
			if (islocal) {
				avatar = SiteUtil.getAvatar(ctx);
			} else {
				if (!TextUtils.isEmpty(jid)) {
					avatar = SiteUtil.downloaAvatar(ctx, peer, true);
				}
			}
			
			if (avatar == null) {
				sendRedirect(res, def);
				return true;
			}
			//
			sendFile(req, res, avatar, IMAGE_ANY);
			
			return true;
		}
	
		return false;
	}

	private boolean handleTemp(Uri uri, String p, HttpRequest req,
			HttpResponse res, HttpContext context) throws IOException {
		if (p.startsWith("/tmp/")) {
			final File tempDir = ApplicationContext.getTempDir(ctx);
			final String name = uri.getLastPathSegment();
			File file = new File(tempDir, name);
			String mime = guessContentType(name, APPLICATION_OCTET_STREAM);
			file.setLastModified(System.currentTimeMillis());
			
			sendFile(req, res, file, mime);
			return true;
		}
		return false;
	}

	private boolean handleDrawableRes(Uri uri, String p, HttpRequest req,
			HttpResponse res, HttpContext context) throws Exception {
		// drawable for now
		final boolean isRes = (p.startsWith("/drawable/") || p
				.startsWith("/res/drawable/"));
		if (!isRes) {
			return false;
		}
		if (p.equals("/drawable/index")) {
			ArrayList<JSONObject> al = new ArrayList<JSONObject>();
			// android
			al.addAll(asList(ResourceConstants.ANDROID_DRAWABLE_RES));

			//
			al.addAll(asList(ResourceConstants.DRAWABLE_RES));

			JSONObject data = new JSONObject();
			data.put("index", 1);
			data.put("content", al);
			sendAssetData(req, res, "web/layout/list.tpl", data);
			return true;
		}
		final String name = uri.getLastPathSegment();
		final String bname = Files.basename(name);
		final int resId = mapDrawbleResource(bname);
		final String type = guessContentType(name, IMAGE_ANY);
		
		sendDrawable(req, res, bname, resId, type);
		
		return true;
	}

	private Collection<? extends JSONObject> asList(Object[][] arrays)
			throws JSONException {
		// return list of res names
		List<JSONObject> l = new ArrayList<JSONObject>();
		for (int i = 0; i < arrays.length; i++) {
			JSONObject j = new JSONObject();
			j.put("name", arrays[i][1]);
			l.add(j);
		}
		return l;
	}

	private int mapResource(String path) {
		String p = ctx.getPackageName() + ":" + path;
		return ctx.getResources().getIdentifier(p, null, null);
	}
	
	private int mapDrawbleResource(String name) {
		// android
		Object[][] res = ResourceConstants.ANDROID_DRAWABLE_RES;
		for (int i = 0; i < res.length; i++) {
			if (res[i][1].equals(name)) {
				return (Integer) res[i][0];
			}
		}
		// 
		res = ResourceConstants.DRAWABLE_RES;
		for (int i = 0; i < res.length; i++) {
			if (res[i][1].equals(name)) {
				return (Integer) res[i][0];
			}
		}
		
		return mapResource("drawable/"+name);
	}

	private boolean handleResource(Uri uri, String p, HttpRequest req,
			HttpResponse res, HttpContext context) throws Exception {
		if (!p.startsWith("/resource/")) {
			return false;
		}
		File f = getCacheFile(p);
		if (!f.exists()) {
			boolean found = ResourceUtils.copyClassResource(p, f);
			if (!found) {
				throw new NotFoundException(p);
			}
			f.setLastModified(System.currentTimeMillis());
		}
		final String type = guessContentType(p, APPLICATION_OCTET_STREAM);
		sendFile(req, res, f, type);
		return true;
	}

	private boolean handleAppIcon(Uri uri, String p, HttpRequest req,
			HttpResponse res, HttpContext context) throws Exception {
		if (p.startsWith("/icon/index")) {
			JSONObject data = new JSONObject();
			//
			List<ResourceInfo> al = AppHelper.getAllApps(ctx);
			data.put("index", 0);
			data.put("content", al);
			sendAssetData(req, res, "web/layout/list.tpl", data);
			return true;
		}
		if (p.startsWith("/icon/")) {
			final int idx1 = 6;
			String pkg = p.substring(idx1);
			File file = getIconFile(pkg);
			//
			sendFile(req, res, file, IMAGE_ANY);
			
			return true;
		}
		return false;
	}

	private boolean handleWallpaper(Uri uri, String p, HttpRequest req,
			HttpResponse res, HttpContext context) throws Exception {
		if (!p.equals("/wallpaper") && !p.startsWith("/wallpaper/")) {
			return false;
		}
		
		int sel = BackgroundImages.BG_SELECT_DEFAULT;
		if (p.equals("/wallpaper")) {
			sel = Preferences.getBackgroundSelection(ctx,
					BackgroundImages.BG_SELECT_DEFAULT);
		} else {
			sel = parseInt(uri.getLastPathSegment(),
					BackgroundImages.BG_SELECT_DEFAULT);
		}
		
		final String pick = uri.getQueryParameter("path");
		if (!TextUtils.isEmpty(pick)) {
			sel = BackgroundImages.BG_SELECT_PICK;
		}
		
		File f = BackgroundImages.loadBackground(ctx, sel, pick);

		if (Debug.DEBUG) {
			Log.d(TAG, "" + "handleWallpaper sel: " + sel + " pick: " + pick + " file: " + f);
		}
		sendFile(req, res, f, IMAGE_ANY );
		
		return true;
	}

	protected final void sendFile(HttpRequest req, HttpResponse res, File file,
			String mimeType) throws IOException {
		//path + timestamp as etag
		final String etag = toHex(file.getPath() + "|" + file.lastModified());
		if(sentNotModified(req, res, mimeType, etag)) {
			return;
		}
		
		setCacheControlHeader(res);
		setETagHeader(res, etag);
		
		super.sendFile(req, res, file, mimeType);
	}

	private boolean handleAsset(Uri uri, String p, HttpRequest req,
			HttpResponse res, HttpContext context) throws Exception {
		if (!isAsset(p)) {
			return false;
		}
		String name = p.startsWith("/assets/") ? p.substring(8) : p
				.substring(1);
		//
		// asset browsing?
		//
		if (name.startsWith("index")) {
			sendAsset(req, res, context, "web/layout/assets.tpl", TEXT_HTML);
			return true;
		}
		if (name.startsWith("bin/list")) {
			final AssetManager am = ctx.getAssets();
			final String u = uri.getQueryParameter("path");
			JSONObject data = listAssets(am, u);
			sendJSONData(req, res, context, data);
			return true;
		}
		if (name.startsWith("bin/item")) {
			final String u = uri.getQueryParameter("path");
			final String type = uri.getQueryParameter("type");
			sendAsset(req, res, context, u, type);
			return true;
		}
		//
		if (p.startsWith("/image/")) {
			String mime = guessContentType(name, IMAGE_ANY);
			sendAsset(req, res, context, name, mime);
			return true;
		}
		if (p.startsWith("/script/")) {
			sendAsset(req, res, context, name, TEXT_JAVASCRIPT);
			return true;
		}
		if (p.startsWith("/style/")) {
			sendAsset(req, res, context, name, TEXT_CSS);
			return true;
		}

		//
		final String type = guessContentType(name, APPLICATION_OCTET_STREAM);
		sendAsset(req, res, context, name, type);
		return true;
	}

	private boolean isAsset(String p) {
		final String[] paths = new String[] { "/assets/", "/style/",
				"/script/", "/image/", "/web/", "/pub/", "/opt/" };
		for (int i = 0; i < paths.length; i++) {
			if (p.startsWith(paths[i])) {
				return true;
			}
		}
		//
		return false;
	}

	private File getIconFile(String pkg) throws Exception {
		File file = ApplicationContext.getIconFile(ctx, pkg);
		if (!file.exists()) {
			if (Debug.DEBUG) {
				Log.d(TAG, "getIconFile file: " + file);
			}
			// cache icon
			Drawable image = pm.getApplicationIcon(pkg);
			String name = ApplicationContext.getIconName(pkg);
			CompressFormat cf = CompressFormat.PNG;
			
			save(image, name, 0, cf);
		}
		return file;
	}

	protected void save(Drawable image, String name, int alpha,
			CompressFormat cf) throws IOException {
		FileOutputStream fos = null;
		try {
			fos = ctx.openFileOutput(name, Context.MODE_PRIVATE);
			Bitmap bitmap = null;
			if (image instanceof BitmapDrawable) {
				bitmap = ((BitmapDrawable) image).getBitmap();
			} else {
				final Config bc = Bitmap.Config.ARGB_8888;
				final int w = image.getIntrinsicWidth();
				final int h = image.getIntrinsicHeight();
				bitmap = Bitmap.createBitmap(w, h, bc);
				Canvas c = new Canvas(bitmap);
				//
				image.setBounds(0, 0, w, h);
				image.setAlpha(alpha);
				image.setDither(true);
				image.draw(c);
			}
			//
			final int q = 100;
			bitmap.compress(cf, q, fos);
			
			ctx.getFileStreamPath(name).setLastModified(System.currentTimeMillis());
		} finally {
			if (fos != null) {
				fos.close();
			}
		}
	}

	private JSONObject listAssets(AssetManager am, final String path)
			throws JSONException {
		JSONObject jo = new JSONObject();
		final long start = System.currentTimeMillis();
		final boolean isEmpty = TextUtils.isEmpty(path);
		final boolean isRoot = (isEmpty || path.equals("/"));
		try {
			final String relpath = (isRoot ? "" : (path.charAt(0) == '/' ? path
					.substring(1) : path));
			String items[] = am.list(relpath);
			if (items == null) {
				jo.put("count", 0);
			} else {
				ArrayList<JSONObject> list = new ArrayList<JSONObject>();
				jo.put("count", items.length);
				for (int i = 0; i < items.length; i++) {
					String n = items[i];
					FileItem item = new FileItem(n);
					String t = mapAssetType(n);
					item.type = (t == null ? guessContentType(n, null) : t);
					if (item.type == null) {
						String[] sub = am.list(isRoot ? n : relpath + "/" + n);
						item.isFile = (sub == null || sub.length == 0);
					} else {
						item.isFile = true;
					}
					list.add(item.toJSONObject());
				}
				jo.put("list", list);
				jo.put("path", "/" + relpath);
				jo.put("isFile", (items.length == 0));
			}
		} catch (Exception e) {
			e.printStackTrace();
			jo.put("error", e.getMessage());
		}
		jo.put("elapsed", (System.currentTimeMillis() - start));
		return jo;
	}

	private String mapAssetType(String n) {
		// files without an extention
		final String[] names = { "root", "AUTHORS", "ChangeLog", "COPYING",
				"INSTALL", "README", };
		for (int i = 0; i < names.length; i++) {
			if (n.equals(names[i])) {
				return "text/plain";
			}
		}
		return null;
	}

	protected String[][] listRes(String pkg) throws Exception {
		try {
			PackageManager pm = ctx.getPackageManager();
			Resources res = pm.getResourcesForApplication(pkg);
			Context pkgctx = ctx.createPackageContext(pkg,
					Context.CONTEXT_INCLUDE_CODE
							| Context.CONTEXT_IGNORE_SECURITY);
			ClassLoader cl = pkgctx.getClassLoader();
			Class<?> c = cl.loadClass(pkg+".R");
			Class<?>[] ca = c.getDeclaredClasses();
			String[][] raa = new String[ca.length][];
			for (int i = 0; i < ca.length; i ++) {
				Field[] fa = ca[i].getDeclaredFields();
				for (int j = 0; j < fa.length; j++) {
					int resid = fa[j].getInt(null);
					String pn = res.getResourcePackageName(resid);
					String tn = res.getResourceTypeName(resid);
					String rn = res.getResourceName(resid);
					raa[i] = new String[] {pn, tn, rn};
				}
			}
			return raa;
		} catch (NameNotFoundException e) {
			return null;
		} catch (Exception e) {
			throw e;
		}
	}
	
	protected String[] listAssets(String pkg, String path) throws Exception {
		try {
			PackageManager pm = ctx.getPackageManager();
			Resources res = pm.getResourcesForApplication(pkg);
			AssetManager assets = res.getAssets();
			//
			if (path == null) {
				path = "";
			} else if (path.startsWith("/")){
				path = path.substring(1);
			}
			String[] pa = assets.list(path);
			return pa;
		} catch (NameNotFoundException e) {
			return null;
		} catch (Exception e) {
			throw e;
		}
	}
	
	protected InputStream getAssets(String pkg, String name) throws Exception {
		try {
			PackageManager pm = ctx.getPackageManager();
			Resources res = pm.getResourcesForApplication(pkg);
			AssetManager assets = res.getAssets();
			InputStream is = assets.open(name);
			return is;
		} catch (NameNotFoundException e) {
			return null;
		} catch (Exception e) {
			throw e;
		}
	}

	protected InputStream getResource(String pkg, String className) throws Exception  {
		try {
			Context pkgctx = ctx.createPackageContext(pkg, Context.CONTEXT_IGNORE_SECURITY);
			ClassLoader cl = pkgctx.getClassLoader();
			InputStream is = cl.getResourceAsStream(className);
			return is;
		} catch (NameNotFoundException e) {
			return null;
		} catch (Exception e) {
			throw e;
		}
	}

}
